﻿/*******************************************************************************
 * AAEngine
 * Copyright (c) 2010 Mike Jarosch
 *
 * Original source PushButton Engine:
 * Copyright (C) 2009 PushButton Labs, LLC
 * For more information see http://www.pushbuttonengine.com
 *
 * This file is licensed under the terms of the MIT license, which is included
 * in the "MIT License.txt" file at the root directory of this SDK.
 ******************************************************************************/

using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using AAEngine.Engine;
using AAEngine.Engine.Entities;

namespace AAEngine.Rendering2D.SpriteSheet
{
    /// <summary>
    /// An abstract class to allow access to a set of sprites.
    /// This needs to be inherited to be of any use.
    /// </summary>
    public abstract class SpriteContainerComponent : EntityComponent
    {
        protected Texture2D[] _frames;
        protected Vector2? _center;
        protected bool _defaultCenter = true;
        protected int _frameCountCap;
        protected FrameNote[] _frameNotes;

        public SpriteContainerComponent()
        {
            DirectionsPerFrame = 1;
        }

        /// <summary>
        /// Subclasses must override this method and return an
        /// array of Rectangle objects.
        /// </summary>
        /// <returns></returns>
        protected virtual Texture2D[] GetSourceFrames()
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// Deletes the frames so this class can be re-used with a new set of frames.
        /// </summary>
        protected void DeleteFrames()
        {
            _frames = null;
        }

        /// <summary>
        /// True if the frames associated with this sprite container have been loaded.
        /// </summary>
        public virtual bool IsLoaded
        {
            get { return _frames != null; }
        }

        /// <summary>
        /// Specifies an offset so the sprite is centered correctly. If it is not
        /// set, the sprite is centered.
        /// </summary>
        public Vector2? Center
        {
            get
            {
                if (!_center.HasValue)
                {
                    return Vector2.Zero;
                }
                return _center.Value;
            }
            set
            {
                _center = value;
                _defaultCenter = false;
            }
        }

        /// <summary>
        /// Indicates that the spriteSheet has a centered alignment.
        /// </summary>
        public bool Centered
        {
            get { return _defaultCenter; }
        }

        /// <summary>
        /// The number of directions per frame.
        /// </summary>
        public int DirectionsPerFrame { get; set; }

        /// <summary>
        /// The number of degrees separating each direction.
        /// </summary>
        public float DegreesPerDirection
        {
            get { return 360 / DirectionsPerFrame; }
        }

        public int RawFrameCount
        {
            get
            {
                if (_frames == null)
                {
                    BuildFrames();
                }

                return _frames != null ? _frames.Length : 0;
            }
        }

        /// <summary>
        /// number of frames the sprite container has. This counts each set of
        /// directions as one frame.
        /// </summary>
        public int FrameCount
        {
            get { return RawFrameCount / DirectionsPerFrame; }
            set
            {
                if (_frames == null)
                {
                    // frames where not loaded yet so cap them as soon as 
                    // the divider provides the frames
                    _frameCountCap = value;
                }
                else
                {
                    if (_frames.Length > value)
                    {
                        Texture2D[] tmp = new Texture2D[value];
                        Array.Copy(_frames, tmp, value);
                        _frames = tmp;
                    }
                }
            }
        }

#if WINDOWS_PHONE || XBOX
        public Texture2D GetFrame(int index)
        {
            return GetFrame(index, 0);
        }

        public Texture2D GetFrame(int index, float direction)
#else
        public Texture2D GetFrame(int index, float direction = 0f)
#endif
        {
            if (!IsLoaded)
            {
                return null;
            }

            while (direction < 0)
            {
                direction += 360;
            }

            while (direction > 360)
            {
                direction -= 360;
            }

            if (DirectionsPerFrame == 1)
            {
                return GetRawFrame(index);
            }

            if (_frameNotes == null)
            {
                GenerateFrameNotes();
            }

            int bestMatchIndex = -1;
            float bestMatchDirectionDistance = float.PositiveInfinity;

            for (int i = 0; i < _frameNotes.Length; i++)
            {
                FrameNote note = _frameNotes[i];
                if (note.Frame != index)
                {
                    continue;
                }

                float distance = Math.Min(Math.Abs(note.Direction - direction), note.Direction + 360 - direction);
                if (distance < bestMatchDirectionDistance)
                {
                    bestMatchDirectionDistance = distance;
                    bestMatchIndex = note.RawFrame;
                }
            }

            if (bestMatchIndex >= 0)
            {
                return GetRawFrame(bestMatchIndex);
            }

            return null;
        }

        protected void BuildFrames()
        {
            _frames = GetSourceFrames();

            if (_frames == null)
            {
                return;
            }

            if (_frames.Length == 0)
            {
                throw new AAException("No frames loaded.");
            }

            if (_frameCountCap > 0)
            {
                Texture2D[] tmp = new Texture2D[_frameCountCap];
                Array.Copy(_frames, tmp, _frameCountCap);
                _frames = tmp;
            }

            if (_defaultCenter)
            {
                _center = new Vector2(_frames[0].Width * 0.5f, _frames[0].Height * 0.5f);
            }
        }

        /// <summary>
        /// Gets the frame at the specified index. This does not take direction into
        /// account.
        /// </summary>
        /// <param name="index"></param>
        /// <returns></returns>
        protected Texture2D GetRawFrame(int index)
        {
            if (_frames == null)
            {
                BuildFrames();
            }

            if (_frames == null)
            {
                return null;
            }

            if ((index < 0) || (index >= RawFrameCount))
            {
                return null;
            }

            return _frames[index];
        }

        private void GenerateFrameNotes()
        {
            _frameNotes = new FrameNote[DirectionsPerFrame * FrameCount];

            int index = 0;
            for (int direction = 0; direction < DirectionsPerFrame; direction++)
            {
                for (int frame = 0; frame < FrameCount; frame++)
                {
                    FrameNote note = new FrameNote();
                    note.Frame = frame;
                    note.Direction = direction * DegreesPerDirection;
                    note.RawFrame = (direction * FrameCount) + frame;
                    _frameNotes[index] = note;
                    index++;
                }
            }
        }
    }

    public sealed class FrameNote
    {
        public int Frame { get; set; }
        public float Direction { get; set; }
        public int RawFrame { get; set; }
    }
}
